package org.zhq.server;

import org.zhq.Foo;
import org.zhq.box.StringReceivePacket;
import org.zhq.core.Connector;
import org.zhq.core.ScheduleTask;
import org.zhq.core.schedule.IdleTimeoutScheduleTask;
import org.zhq.handle.ConnectorCloseChain;
import org.zhq.handle.ConnectorHandler;
import org.zhq.handle.ConnectorStringPacketChain;
import org.zhq.server.audio.AudioRoom;
import org.zhq.util.CloseUtils;

import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

/**
 * @author zhengquan
 * @date 2018/12/22
 */
public class TCPServer implements ServerAcceptor.AcceptListener, Group.GroupMessageAdapter {

    private final File cachePath;
    private final int port;
    private ServerAcceptor acceptor;
    private final List<ConnectorHandler> connectorHandlerList = new CopyOnWriteArrayList<>();
    private final Map<String, Group> groups = new HashMap<>();
    private ServerSocketChannel server;

    private final ServerStatistics serverStatistics = new ServerStatistics();

    public TCPServer(int port, File cachePath) {
        this.port = port;
        this.cachePath = cachePath;
    }

    public boolean start() {
        try {
            ServerAcceptor acceptor = new ServerAcceptor(this);
            server = ServerSocketChannel.open();
            //设置为非阻塞
            server.configureBlocking(false);
            //绑定本地端口
            server.socket().bind(new InetSocketAddress(port));
            //注册客户端连接到达监听
            server.register(acceptor.getSelector(), SelectionKey.OP_ACCEPT);
            //启动客户端监听
            this.acceptor = acceptor;
            acceptor.start();
            if (acceptor.awaitRunning()) {
                System.out.println("服务器准备就绪~~");
                System.out.println("服务器信息:" + server.getLocalAddress().toString());
                return true;
            } else {
                System.out.println("启动异常");
                return false;
            }
        } catch (IOException e) {
            return false;
        }
    }


    public void stop() {
        if (acceptor != null) {
            acceptor.exit();
        }
        synchronized (connectorHandlerList) {
            for (ConnectorHandler clientHandler : connectorHandlerList) {
                clientHandler.exit();
            }
            connectorHandlerList.clear();
        }
        CloseUtils.close(server);
    }

    void broadcast(String str) {
        str = "系统通知:" + str;
        synchronized (connectorHandlerList) {
            for (ConnectorHandler clientHandler : connectorHandlerList) {
                sendMessageToClient(clientHandler, str);
            }
        }
    }

    @Override
    public void sendMessageToClient(ConnectorHandler handler, String msg) {
        handler.send(msg);
        serverStatistics.sendSize++;
    }

    Object[] getStatusString() {
        return new String[]{
                "客户端数量:" + connectorHandlerList.size(),
                "发送数量:" + serverStatistics.sendSize,
                "接收数量:" + serverStatistics.receiveSize
        };
    }


    @Override
    public void onNewSocketArrived(SocketChannel channel) {
        try {
            ConnectorHandler connectorHandler = new ConnectorHandler(channel, cachePath);
            System.out.println("客户端[" + connectorHandler.getClientInfo() + "]已连接");
            connectorHandler.getStringPacketChain()
                    .appendLast(serverStatistics.statisticsChain())
                    .appendLast(new ParseCommandConnectorStringPacketChain())
                    .appendLast(new ParseAudioStreamCommandStringPacketChain());


            connectorHandler.getCloseChain()
                    .appendLast(new RemoveQueueOnConnectorClosedChain())
                    .appendLast(new RemoveAudioQueueOnConnectorClosedChain());

            ScheduleTask scheduleTask = new IdleTimeoutScheduleTask(20, TimeUnit.SECONDS, connectorHandler);
            connectorHandler.schedule(scheduleTask);

            synchronized (connectorHandlerList) {
                connectorHandlerList.add(connectorHandler);
                System.out.println("当前客户端数量:" + connectorHandlerList.size());
            }
            //回送当前连接的唯一标示
            sendMessageToClient(connectorHandler, Foo.COMMAND_INFO_NAME + connectorHandler.getKey().toString());
            sendMessageToClient(connectorHandler, Foo.COMMAND_HELP);

        } catch (IOException e) {
            System.out.println("客户端连接异常:" + e.getMessage());
        }
    }

    private class RemoveQueueOnConnectorClosedChain extends ConnectorCloseChain {

        @Override
        protected boolean consume(ConnectorHandler handler, Connector connector) {
            synchronized (connectorHandlerList) {
                connectorHandlerList.remove(handler);
            }
            return true;
        }
    }

    private class ParseCommandConnectorStringPacketChain extends ConnectorStringPacketChain {

        @Override
        protected boolean consume(ConnectorHandler handler, StringReceivePacket stringReceivePacket) {
            String str = stringReceivePacket.entity();
            if (!str.startsWith(Foo.COMMAND_GROUP_PREFIX)) {
                return false;
            }
            str = str.substring(Foo.COMMAND_GROUP_PREFIX.length());
            if (str.startsWith(Foo.COMMAND_GROUP_CREATE)) {
                String groupName = str.split(" ")[1];
                if (!groups.containsKey(groupName)) {
                    Group group = new Group(groupName, TCPServer.this);
                    group.addMember(handler);
                    groups.put(groupName, group);
                    sendMessageToClient(handler, "create and join [" + groupName + "] group");
                } else {
                    sendMessageToClient(handler, "[" + groupName + "] group is exits");
                }
                return true;

            } else if (str.startsWith(Foo.COMMAND_GROUP_JOIN)) {
                String groupName = str.split(" ")[1];
                Group group = groups.get(groupName);
                if (group == null) {
                    sendMessageToClient(handler, "[" + groupName + "] group is not exits");
                } else if (group.addMember(handler)) {
                    sendMessageToClient(handler, "join [" + groupName + "] group");
                } else {
                    sendMessageToClient(handler, "cannot join [" + groupName + "] group");
                }
                return true;
            } else if (str.startsWith(Foo.COMMAND_GROUP_LEAVE)) {
                String groupName = str.split(" ")[1];
                Group group = groups.get(groupName);
                if (group == null) {
                    sendMessageToClient(handler, "[" + groupName + "] group is not exits");
                } else if (group.removeMember(handler)) {
                    sendMessageToClient(handler, "leave [" + groupName + "] group");
                } else {
                    sendMessageToClient(handler, "cannot leave [" + groupName + "] group");
                }
                return true;
            }
            return false;
        }

        @Override
        protected boolean consumeAgain(ConnectorHandler handler, StringReceivePacket stringReceivePacket) {
            //捡漏模式,当我们第一遍未消费,然后又没有加入到群,自然没有后续的节点消费
            //此时我们进行二次消费,返回发送过来的消息
            sendMessageToClient(handler, stringReceivePacket.entity());
            return true;
        }
    }

    private class ParseAudioStreamCommandStringPacketChain extends org.zhq.handle.ConnectorHandlerChain<StringReceivePacket> {

        @Override
        protected boolean consume(ConnectorHandler handler, StringReceivePacket stringReceivePacket) {
            String str = stringReceivePacket.entity();
            if (str.startsWith(Foo.COMMAND_CONNECTOR_BIND)) {
                //绑定命令,也就是将音频流绑定到当前的命令流上
                String key = str.substring(Foo.COMMAND_CONNECTOR_BIND.length());
                ConnectorHandler audioStreamConnector = findConnectorFromKey(key);
                if (audioStreamConnector != null) {
                    audioCmdToStreamMap.put(handler, audioStreamConnector);
                    audioStreamToCmdMap.put(audioStreamConnector, handler);
                    audioStreamConnector.changeToBridge();
                }
            } else if (str.startsWith(Foo.COMMAND_AUDIO_CREATE_ROOM)) {
                ConnectorHandler audioStreamConnector = findAudioStreamConnector(handler);
                if (audioStreamConnector != null) {
                    AudioRoom room = createNewRoom();
                    joinRoom(room, audioStreamConnector);
                    sendMessageToClient(handler, Foo.COMMAND_INFO_AUDIO_ROOM + room.getRoomCode());
                }
            } else if (str.startsWith(Foo.COMMAND_AUDIO_LEAVE_ROOM)) {
                ConnectorHandler audioStreamConnector = findAudioStreamConnector(handler);
                if (audioStreamConnector != null) {
                    //任意一人离开都销毁房间
                    dissolveRoom(audioStreamConnector);
                    sendMessageToClient(handler, Foo.COMMAND_INFO_AUDIO_STOP);
                }
            }else if(str.startsWith(Foo.COMMAND_AUDIO_JOIN_ROOM)){
                ConnectorHandler audioStreamConnector = findAudioStreamConnector(handler);
                if(audioStreamConnector!=null){
                    String roomCode = str.substring(Foo.COMMAND_AUDIO_JOIN_ROOM.length());
                    AudioRoom room = audioRoomMap.get(roomCode);
                    if(room!=null && joinRoom(room,audioStreamConnector)){
                        ConnectorHandler theOtherHandler = room.getTheOtherHandler(audioStreamConnector);

                        theOtherHandler.bindToBridge(audioStreamConnector.getSender());
                        audioStreamConnector.bindToBridge(theOtherHandler.getSender());

                        sendMessageToClient(handler,Foo.COMMAND_INFO_AUDIO_START);
                        sendMessageToClient(theOtherHandler,Foo.COMMAND_INFO_AUDIO_START);
                    }else{
                        sendMessageToClient(handler,Foo.COMMAND_INFO_AUDIO_ERROR);
                    }
                }
            }else {
                return false;
            }
            return true;
        }
    }

    private void dissolveRoom(ConnectorHandler audioStreamConnector) {
        AudioRoom room = audioConnectorRoomMap.get(audioStreamConnector);
        if (room == null) {
            return;
        }
        ConnectorHandler[] connectorHandlers = room.getConnectors();
        for (ConnectorHandler connectorHandler : connectorHandlers) {
            connectorHandler.unBindToBridge();
            audioConnectorRoomMap.remove(connectorHandler);
            if (connectorHandler != audioStreamConnector) {
                sendStreamConnectorMessage(connectorHandler, Foo.COMMAND_INFO_AUDIO_STOP);
            }
        }

        audioRoomMap.remove(room.getRoomCode());
    }

    private final HashMap<ConnectorHandler, AudioRoom> audioConnectorRoomMap = new HashMap<>();

    private boolean joinRoom(AudioRoom room, ConnectorHandler streamConnector) {
        if (room.enterRoom(streamConnector)) {
            return true;
        }
        return false;
    }

    private final HashMap<String, AudioRoom> audioRoomMap = new HashMap<>(50);

    private AudioRoom createNewRoom() {
        AudioRoom room;
        do {
            room = new AudioRoom();
        } while (audioRoomMap.containsKey(room.getRoomCode()));
        audioRoomMap.put(room.getRoomCode(), room);
        return room;
    }

    private ConnectorHandler findAudioStreamConnector(ConnectorHandler handler) {
        ConnectorHandler connectorHandler = audioCmdToStreamMap.get(handler);
        if (connectorHandler == null) {
            sendMessageToClient(handler, Foo.COMMAND_INFO_AUDIO_ERROR);
        }
        return connectorHandler;
    }

    private ConnectorHandler findConnectorFromKey(String key) {
        synchronized (connectorHandlerList) {
            for (ConnectorHandler connectorHandler : connectorHandlerList) {
                if (connectorHandler.getKey().toString().equalsIgnoreCase(key)) {
                    return connectorHandler;
                }
            }
        }
        return null;
    }

    private final HashMap<ConnectorHandler, ConnectorHandler> audioCmdToStreamMap = new HashMap<>();
    private final HashMap<ConnectorHandler, ConnectorHandler> audioStreamToCmdMap = new HashMap<>();

    private class RemoveAudioQueueOnConnectorClosedChain extends org.zhq.handle.ConnectorHandlerChain<Connector> {
        @Override
        protected boolean consume(ConnectorHandler handler, Connector connector) {
            if(audioCmdToStreamMap.containsKey(handler)){
                audioCmdToStreamMap.remove(handler);
            }else if(audioStreamToCmdMap.containsKey(handler)){
                audioStreamToCmdMap.remove(handler);
                dissolveRoom(handler);
            }
            return false;
        }
    }

    private void sendStreamConnectorMessage(ConnectorHandler streamConnector,String msg){
        if(streamConnector!=null){
            ConnectorHandler audioCmdConnector = findAudioCmdConnector(streamConnector);
            sendMessageToClient(audioCmdConnector,msg);
        }
    }

    private ConnectorHandler findAudioCmdConnector(ConnectorHandler streamConnector) {
        return audioStreamToCmdMap.get(streamConnector);
    }
}
